// (C) 2022 Intel Corporation. All rights reserved.
// Your use of Intel Corporation's design tools, logic functions and other 
// software and tools, and its AMPP partner logic functions, and any output 
// files from any of the foregoing (including device programming or simulation 
// files), and any associated documentation or information are expressly subject 
// to the terms and conditions of the Intel Program License Subscription 
// Agreement, Intel FPGA IP License Agreement, or other applicable 
// license agreement, including, without limitation, that your use is for the 
// sole purpose of programming logic devices manufactured by Intel and sold by 
// Intel or its authorized distributors.  Please refer to the applicable 
// agreement for further details.

`timescale 1 ps / 1 ps

`include "espi_header.iv"

module espi_vw_channel
  #(
    parameter VW_FIFO_SIZE = 32,
    parameter VW_FIFO_WIDTHU = 6,
    parameter DATABYTE_ARR = 4,
    parameter DEVICE_FAMILY = "MAX 10 FPGA"
    )
    (
     input                clk,
     input                reset_n,
     input                espi_reset_n, //
     input [2:0]          rx_data_array_cnt,
     input                malform_vw_packet_reg,
     input                pec_mismatch_reg,
     input                vw_channel_en,
     input                rsmrst_n,
     input [7:0]          data_byte[DATABYTE_ARR],
     input                stop_det,
     input                detect_putvwire,
     input                spiclk_cnt_done_hl,
     input                oob_rst_ack,
     input                wake_n,
     input                pme_n,
     input                slave_boot_load_done,
     input                error_condition1,
     input                error_fatal,
     input                error_nonfatal,
     input                slave_boot_load_status,
     input                sci_n,
     input                nmi_n,
     input                smi_n,
     input                rcin_n,
     input                host_rst_ack,
     input                pop_vw_databyte,
     input [6:0]          max_vw_count_int,

     input [255:0]        irq,

     input [255:0]        plti,
     output logic [255:0] plto,

     input [511:0]        gpi,
     output logic [511:0] gpo,

     output logic         vw_channel_ready,
     output logic         vwire_avail,
     output logic [7:0]   vw_data,
     output logic [7:0]   vw_index,
     output logic [6:0]   vw_count,
     output logic         slp_s3_n,
     output logic         slp_s4_n,
     output logic         slp_s5_n,
     output logic         sus_stat_n,
     output logic         pltrst_n,
     output logic         oob_rst_warn,
     output logic         host_rst_warn,
     output logic         smiout_n,
     output logic         nmiout_n,

	 output logic 				  gpi_txfifo_put_stp,
    output logic [15:0]       gpi_txfifo_wdata_stp	  
     );

    logic                      put_fifo;
    logic                      pop_fifo;
    logic                      empty;
    logic                      full;
    logic [7:0]                index;
    logic [7:0]                data;
    logic [VW_FIFO_WIDTHU-1:0] vw_count_num;
    logic                      no_irq_changes;
    logic                      no_sys_changes;
    logic                      no_plt_changes;
    logic                      no_gpi_changes;
    logic                      no_changes;
    logic                      vw_index_rsm_reset_n;
    logic                      vw_index_espi_reset_n;
    logic                      vw_index_plt_reset_n;
    

    assign vw_channel_ready = vw_channel_en;
    assign pop_fifo = pop_vw_databyte;
    assign vw_index_rsm_reset_n = reset_n && rsmrst_n;
    assign vw_index_espi_reset_n = reset_n && espi_reset_n;
    assign vw_index_plt_reset_n = reset_n && pltrst_n;
    assign vw_count = (vw_count_num > max_vw_count_int) ? max_vw_count_int :
                      {{(7-VW_FIFO_WIDTHU){1'b0}}, vw_count_num};

    // Mark available so as to support the most number of events transmitted (up to the limit).  This is to
    // help reduce the impact of the binary search on the latency of reporting events.
    assign vwire_avail = ((~empty & no_changes) | full) & vw_channel_en;
    assign no_changes = no_irq_changes & no_sys_changes & no_plt_changes & no_gpi_changes;

    // ----------------- MASTER TO SLAVE VW PORT -----------------------------------
    localparam VW_DEFAULT = 1024'h0 | (4'h6 << 7*4);
    logic [1023:0] vw;
    logic [1023:0] set_1;
    logic [1023:0] set_0;

    always_comb begin
        slp_s3_n      = vw[2*4+0];
        slp_s4_n      = vw[2*4+1];
        slp_s5_n      = vw[2*4+2];
        sus_stat_n    = vw[3*4+0];
        pltrst_n      = vw[3*4+1];
        oob_rst_warn  = vw[3*4+2];
        host_rst_warn = vw[7*4+0];
        smiout_n      = vw[7*4+1];
        nmiout_n      = vw[7*4+2];

        plto          = vw[64*4+:256];
        gpo           = vw[128*4+:512];
    end // always_comb

    always @(posedge clk, negedge vw_index_espi_reset_n) begin
        if (!vw_index_espi_reset_n) begin
            vw    <= VW_DEFAULT;
            set_1 <= 0;
            set_0 <= 0;
        end
        else begin
            if (stop_det) begin
                set_1 <= 0;
                set_0 <= 0;
                if (vw_channel_ready && !(malform_vw_packet_reg || error_condition1)) begin
                    vw <= (vw & ~set_0) | set_1;
                end
            end
            
            if (detect_putvwire && spiclk_cnt_done_hl && (rx_data_array_cnt == 3'h1)) begin
                for (int i=0; i < 4; i++) begin
                    set_1[data_byte[0]*4+i] <= data_byte[1][i+4] & data_byte[1][i];
                    set_0[data_byte[0]*4+i] <= data_byte[1][i+4] & ~data_byte[1][i];
                end
            end
        end // else: !if(!vw_index_espi_reset_n)
    end // always @ (posedge clk, negedge vw_index_espi_reset_n)

    //------------------------- SLAVE TO MASTER VWIRE PORT ---------------------
    // once the logical state of a virtual wire changes, this VW index and data will be stored in the TX FIFO
    // this VW message will get retrieve and send to eSPI master when the master issue GET_VWIRE
    // Update Interrupt Events
    logic [255:0] vw_irq;
    logic [255:0] irq_changed;
    logic [7:0]   i_irq;
    logic [3:0]   irq_search_state;
    logic         irq_txfifo_put;
    logic [15:0]  irq_txfifo_wdata;
    logic         irq_txfifo_req;
    logic         irq_txfifo_gnt;

    always @(posedge clk, negedge reset_n) begin
        if (!reset_n) begin
            no_irq_changes   <= 1'b1;
            irq_txfifo_put   <= 0;
            irq_txfifo_wdata <= 0;
            vw_irq           <= 0;
            i_irq            <= 0;
            irq_changed      <= 0;
            irq_txfifo_req   <= 0;
            irq_search_state <= 0;
        end
        else begin
            irq_txfifo_put <= 0;
            case (irq_search_state)
              0: begin
                  i_irq          <= 0;
                  irq_txfifo_req <= 0;
                  irq_changed    <= vw_irq ^ irq;
                  if (!full) begin
                      irq_search_state <= 1;
                  end
              end

              1: begin
                  if (irq_changed != 0) begin
                      no_irq_changes   <= 0;
                      irq_search_state <= 2;
                      if (irq_changed[127:0] == 0) begin
                          i_irq <= 8'd128;
                      end
                  end
                  else begin
                      no_irq_changes   <= 1'b1;
                      irq_search_state <= 0;
                  end // else: !if(irq_changed != 0)
              end // case: 1

              2: begin
                  irq_search_state <= 3;
                  if (irq_changed[i_irq+:64] == 0) begin
                      i_irq <= i_irq+8'd64;
                  end
              end

              3: begin
                  irq_search_state <= 4;
                  if (irq_changed[i_irq+:32] == 0) begin
                      i_irq <= i_irq+8'd32;
                  end
              end

              4: begin
                  irq_search_state <= 5;
                  if (irq_changed[i_irq+:16] == 0) begin
                      i_irq <= i_irq+8'd16;
                  end
              end

              5: begin
                  irq_search_state <= 6;
                  if (irq_changed[i_irq+:8] == 0) begin
                      i_irq <= i_irq+8'd8;
                  end
              end

              6: begin
                  irq_search_state <= 7;
                  if (irq_changed[i_irq+:4] == 0) begin
                      i_irq <= i_irq+8'd4;
                  end
              end

              7: begin
                  irq_search_state <= 8;
                  if (irq_changed[i_irq+:2] == 0) begin
                      i_irq <= i_irq+8'd2;
                  end
              end

              8: begin
                  irq_search_state <= 9;
                  irq_txfifo_req   <= 1;
                  if (irq_changed[i_irq+:1] == 0) begin
                      i_irq <= i_irq+8'd1;
                  end
              end

              9: begin
                  if (irq_txfifo_gnt) begin
                      irq_txfifo_put   <= 1;
                      irq_txfifo_wdata <= {irq[i_irq], i_irq[6:0], 7'h0, i_irq[7]};
                      vw_irq[i_irq]    <= irq[i_irq];
                      irq_search_state <= 0;
                  end
              end

              default: begin
                  irq_search_state <= 0;
              end

            endcase // case (irq_search_state)

            if (full) begin
                // Force a return to the initial state until the FIFO is available.
                irq_search_state <= 0;
            end
        end // else: !if(!reset_n)
    end // always @ (posedge clk, negedge reset_n)

    // Update System Event Inputs
    logic [63:0] sys;
    logic [63:0] vw_sys;
    logic [15:0] sys_changed;
    logic [7:0]  i_sys;
    logic [2:0]  sys_search_state;
    logic        sys_txfifo_put;
    logic [15:0] sys_txfifo_wdata;
    logic        sys_txfifo_req;
    logic        sys_txfifo_gnt;

    always @(posedge clk, negedge reset_n) begin
        if (!reset_n) begin
            no_sys_changes   <= 1'b1;
            sys_txfifo_put   <= 0;
            sys_txfifo_wdata <= 0;
            vw_sys           <= 64'h0000_0001_070c_0000;
            i_sys            <= 0;
            sys_changed      <= 0;
            sys_txfifo_req   <= 0;
            sys_search_state <= 0;
        end
        else begin
            case (sys_search_state)
              0: begin
                  i_sys          <= 0;
                  sys_txfifo_req <= 0;
                  sys_txfifo_put <= 0;
                  for (int i=0; i < 9; i++) begin
                      sys_changed[i] <= |(vw_sys[i*4+:4] ^ sys[i*4+:4]);
                  end

                  if (!full) begin
                      sys_search_state <= 1;
                  end
              end // case: 0

              1: begin
                  if (sys_changed != 0) begin
                      no_sys_changes   <= 0;
                      sys_search_state <= 2;
                      if (sys_changed[7:0] == 0) begin
                          i_sys <= 7'd8;
                      end else if (sys_changed[3:0] == 0) begin
                          i_sys <= 7'd4;
                      end
                  end
                  else begin
                      no_sys_changes   <= 1'b1;
                      sys_search_state <= 0;
                  end // else: !if(sys_changed != 0)
              end // case: 1

              2: begin
                  sys_search_state <= 3;
                  if (sys_changed[i_sys+:2] == 0) begin
                      i_sys <= i_sys+7'd2;
                  end
              end

              3: begin
                  sys_search_state <= 4;
                  sys_txfifo_req   <= 1;
                  if (sys_changed[i_sys+:1] == 0) begin
                      i_sys <= i_sys+7'd1;
                  end
              end

              4: begin
                  if (sys_txfifo_gnt) begin
                      sys_txfifo_put     <= 1;
                      sys_txfifo_wdata   <= {vw_sys[i_sys*4+:4] ^ sys[i_sys*4+:4], sys[i_sys*4+:4],
                                             i_sys+8'd0};
                      vw_sys[i_sys*4+:4] <= sys[i_sys*4+:4];
                      sys_search_state   <= 0;
                  end
              end

              default: begin
                  sys_search_state <= 0;
              end

            endcase // case (sys_search_state)

            if (full) begin
                // Force a return to the initial state until the FIFO is available.
                sys_search_state <= 0;
            end
        end // else: !if(!reset_n)
    end // always @ (posedge clk, negedge reset_n)

    always_comb begin
        sys        = 0;
        sys[4*4+0] = oob_rst_ack;
        sys[4*4+2] = wake_n;
        sys[4*4+3] = pme_n;
        sys[5*4+0] = slave_boot_load_done;
        sys[5*4+1] = error_fatal;
        sys[5*4+2] = error_nonfatal;
        sys[5*4+3] = slave_boot_load_status;
        sys[6*4+0] = sci_n;
        sys[6*4+1] = smi_n;
        sys[6*4+2] = rcin_n;
        sys[6*4+3] = host_rst_ack;
        sys[8*4+0] = nmi_n;
    end // always_comb

    // Update Platform Inputs
    logic [255:0] vw_plti;
    logic [63:0]  plti_changed;
    logic [7:0]   i_plti;
    logic [3:0]   plti_search_state;
    logic         plt_txfifo_put;
    logic [15:0]  plt_txfifo_wdata;
    logic         plt_txfifo_req;
    logic         plt_txfifo_gnt;

    always @(posedge clk, negedge reset_n) begin
        if (!reset_n) begin
            no_plt_changes    <= 1'b1;
            plt_txfifo_req    <= 0;
            plt_txfifo_put    <= 0;
            plt_txfifo_wdata  <= 0;
            vw_plti           <= 0;
            i_plti            <= 0;
            plti_changed      <= 0;
            plti_search_state <= 0;
        end
        else begin
            case (plti_search_state)
              0: begin
                  i_plti         <= 0;
                  plt_txfifo_req <= 0;
                  plt_txfifo_put <= 0;
                  for (int i=0; i < 64; i++) begin
                      plti_changed[i] <= |(vw_plti[i*4+:4] ^ plti[i*4+:4]);
                  end

                  if (!full) begin
                      plti_search_state <= 1;
                  end
              end // case: 0

              1: begin
                  if (plti_changed != 0) begin
                      no_plt_changes    <= 0;
                      plti_search_state <= 2;
                      if (plti_changed[31:0] == 0) begin
                          i_plti <= 8'd32;
                      end
                  end
                  else begin
                      no_plt_changes    <= 1'b1;
                      plti_search_state <= 0;
                  end // else: !if(plti_changed != 0)
              end // case: 1

              2: begin
                  plti_search_state <= 3;
                  if (plti_changed[i_plti+:16] == 0) begin
                      i_plti <= i_plti+8'd16;
                  end
              end

              3: begin
                  plti_search_state <= 4;
                  if (plti_changed[i_plti+:8] == 0) begin
                      i_plti <= i_plti+8'd8;
                  end
              end

              4: begin
                  plti_search_state <= 5;
                  if (plti_changed[i_plti+:4] == 0) begin
                      i_plti <= i_plti+8'd4;
                  end
              end

              5: begin
                  plti_search_state <= 6;
                  if (plti_changed[i_plti+:2] == 0) begin
                      i_plti <= i_plti+8'd2;
                  end
              end

              6: begin
                  plti_search_state <= 7;
                  plt_txfifo_req    <= 1;
                  if (plti_changed[i_plti+:1] == 0) begin
                      i_plti <= i_plti+8'd1;
                  end
              end

              7: begin
                  if (plt_txfifo_gnt) begin
                      plt_txfifo_put   <= 1;
                      plt_txfifo_wdata <= {vw_plti[i_plti*4+:4] ^ plti[i_plti*4+:4], plti[i_plti*4+:4],
                                           i_plti+8'h40};
                      vw_plti[i_plti*4+:4] <= plti[i_plti*4+:4];
                      plti_search_state    <= 0;
                  end
              end

              default: begin
                  plti_search_state <= 0;
              end

            endcase // case (plti_search_state)

            if (full) begin
                // Force a return to the initial state until the FIFO is available.
                plti_search_state <= 0;
            end
        end // else: !if(!reset_n)
    end // always @ (posedge clk, negedge reset_n)

	 
    // Update General Purpose Inputs
    logic            gpi_txfifo_put;
    logic [15:0]     gpi_txfifo_wdata;
    logic            gpi_txfifo_req;
    logic            gpi_txfifo_gnt;
    logic [3:0]      gpi_no_changes;
    logic [3:0]      gpi_put;
    logic [15:0]     gpi_wdata[4];
    logic [3:0]      gpi_request;
    logic [3:0]      gpi_grant;

	 always @(posedge clk, negedge reset_n) begin
        if (!reset_n) begin;
            gpi_txfifo_put_stp   <= 0;
				gpi_txfifo_wdata_stp <= 0;
        end
		  else begin
				gpi_txfifo_put_stp <= gpi_txfifo_put;
				gpi_txfifo_wdata_stp <= gpi_txfifo_wdata;
		  end
	 end		  

    assign no_gpi_changes = |gpi_no_changes;

    assign gpi_txfifo_put = (gpi_grant[0] & gpi_put[0]) |
                            (gpi_grant[1] & gpi_put[1]) |
                            (gpi_grant[2] & gpi_put[2]) |
                            (gpi_grant[3] & gpi_put[3]);

    assign gpi_txfifo_wdata = gpi_grant[0] ? gpi_wdata[0] :
                              gpi_grant[1] ? gpi_wdata[1] :
                              gpi_grant[2] ? gpi_wdata[2] :
                              gpi_grant[3] ? gpi_wdata[3] :
                              16'h0;
    
    assign gpi_grant[0] = gpi_txfifo_gnt & gpi_request[0]                      & ~|gpi_grant[3:1] & reset_n;
    assign gpi_grant[1] = gpi_txfifo_gnt & gpi_request[1] & ~|gpi_request[0:0] & ~|gpi_grant[3:2] & reset_n;
    assign gpi_grant[2] = gpi_txfifo_gnt & gpi_request[2] & ~|gpi_request[1:0] & ~|gpi_grant[3:3] & reset_n;
    assign gpi_grant[3] = gpi_txfifo_gnt & gpi_request[3] & ~|gpi_request[2:0]                    & reset_n;

    assign gpi_txfifo_req = |gpi_request;

    genvar q;
    generate
        for (q = 0; q < 4; q++) begin : vgu
            espi_vw_gpi_update
                 #(
                   .BASE(q*128/4)
                   )
            vw_gpi_update
                 (
                  .no_gpi_changes (gpi_no_changes[q]),
                  .gpi            (gpi[q*128+:128]),
                  .request        (gpi_request[q]),
                  .grant          (gpi_grant[q]),
                  .full           (full),
                  .put            (gpi_put[q]),
                  .wdata          (gpi_wdata[q]),
                  .clk            (clk),
                  .reset_n        (reset_n)
                  );
        end // for (q = 0; q < 4; q++)
    endgenerate
            

    always @(posedge clk, negedge reset_n) begin
        if (!reset_n) begin
        end
        else begin
        end
    end

    logic        put;
    logic [15:0] wdata;

    espi_fifo
      #(
        .DSIZE  (16),
        .DEPTH  (VW_FIFO_SIZE),
        .WIDTHU (VW_FIFO_WIDTHU),
        .FAMILY (DEVICE_FAMILY)
        )
    vw_channel_inst
      (
       .clk   (clk),
       .rst_n (reset_n),
       .put   (put),
       .get   (pop_fifo),
       .wdata (wdata),
       .full  (full),
       .empty (empty),
       .rdata ({vw_data, vw_index}),
       .usedw (vw_count_num)
       );

    assign put = ~full & ((irq_txfifo_gnt & irq_txfifo_put) |
                          (sys_txfifo_gnt & sys_txfifo_put) |
                          (plt_txfifo_gnt & plt_txfifo_put) |
                          (gpi_txfifo_gnt & gpi_txfifo_put));

    assign wdata = irq_txfifo_gnt ? irq_txfifo_wdata :
                   sys_txfifo_gnt ? sys_txfifo_wdata :
                   plt_txfifo_gnt ? plt_txfifo_wdata :
                   gpi_txfifo_gnt ? gpi_txfifo_wdata :
                   16'h0;

    // Arbitrate the FIFO
    assign gpi_txfifo_gnt = (gpi_txfifo_gnt | ~(irq_txfifo_req | sys_txfifo_req | plt_txfifo_req)) &
                            gpi_txfifo_req & reset_n;
    assign plt_txfifo_gnt = (plt_txfifo_gnt | ~(irq_txfifo_req | sys_txfifo_req | gpi_txfifo_gnt)) &
                            plt_txfifo_req & reset_n;
    assign sys_txfifo_gnt = (sys_txfifo_gnt | ~(irq_txfifo_req | plt_txfifo_gnt | gpi_txfifo_gnt)) &
                            sys_txfifo_req & reset_n;
    assign irq_txfifo_gnt = (irq_txfifo_gnt | ~(sys_txfifo_gnt | plt_txfifo_gnt | gpi_txfifo_gnt)) &
                            irq_txfifo_req & reset_n;

    //------------------------- SLAVE TO MASTER VWIRE PORT ---------------------

endmodule // espi_vw_channel
